Conversation
…nition, create GeyserEntityType, target custom entities MCPL
…can now be modified in the ServerSpawnEntityEvent
There was a problem hiding this comment.
Pull request overview
This PR implements a Custom Entity API for Geyser, refactoring entity creation to use a context-based approach instead of individual constructor parameters. The changes enable support for custom entities while modernizing the entity type system.
Key Changes:
- Introduced
EntitySpawnContextas a unified way to pass entity creation parameters - Renamed
EntityDefinitiontoEntityTypeDefinitionfor clarity - Changed
EntityTypetoBuiltinEntityTypeto distinguish vanilla entities from custom ones - Added new registries for custom entities and Bedrock entity definitions
- Refactored 100+ entity class constructors to use the new context pattern
Reviewed changes
Copilot reviewed 192 out of 193 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| gradle/libs.versions.toml | Updated mcprotocollib version to feature branch for custom entities |
| Test files | Updated mock entity creation to use EntitySpawnContext |
| StatisticsUtils.java | Changed entity name translation to use GeyserEntityType |
| EntityUtils.java | Refactored entity type comparisons from switch to if-else with .is() method |
| Translator classes | Updated to use BuiltinEntityType and new entity creation patterns |
| Session/cache classes | Updated entity instantiation with EntitySpawnContext |
| Registry classes | Added new registries for custom entity support |
| Entity hierarchy | All entity constructors refactored to accept EntitySpawnContext |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
...a/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java
Outdated
Show resolved
Hide resolved
...in/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/entity/BedrockEntityDefinition.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/entity/NonVanillaEntityTypeDefinition.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 222 out of 223 changed files in this pull request and generated 11 comments.
Comments suppressed due to low confidence (1)
core/src/main/java/org/geysermc/geyser/session/GeyserSession.java:610
- getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
getAttachedFireworkRockets exposes the internal representation stored in field attachedFireworkRockets. The value may be modified after this call to getAttachedFireworkRockets.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
core/src/main/java/org/geysermc/geyser/session/cache/waypoint/GeyserWaypoint.java
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/entity/GeyserEntityType.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 130 out of 131 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 130 out of 131 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
core/src/main/java/org/geysermc/geyser/item/parser/ItemStackParser.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/geysermc/geyser/impl/entity/HitboxImpl.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 130 out of 131 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| if (!context.callParrotEvent(this, variant.getAsInt(), !isLeft)) { | ||
| GeyserImpl.getInstance().getLogger().debug(session, "Cancelled parrot spawn as definition is null!"); | ||
| return; |
| GeyserImpl.getInstance().getLogger().debug("Client %s tried to request pack with an invalid id %s)", | ||
| session.bedrockUsername(), id); |
This will be later reworked to allow setting a height + width combo that accounts for the entity pose. For now, it's a general override
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 130 out of 131 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| } else if (entity == null && this.vehicle instanceof BoatEntity) { | ||
| this.vehicle.setBoundingBoxWidth(this.vehicle.getDefinition().width()); | ||
| this.vehicle.setBoundingBoxHeight(this.vehicle.getDefinition().height()); | ||
| this.vehicle.setBoundingBoxWidth(this.vehicle.getBoundingBoxWidth()); | ||
| this.vehicle.setBoundingBoxHeight(this.vehicle.getBoundingBoxHeight()); | ||
| this.vehicle.updateBedrockMetadata(); |
| @Override | ||
| public @Nullable GeyserEntity byJavaId(@NonNegative int javaId) { | ||
| //noinspection ConstantValue | ||
| if (javaId < 0) { | ||
| throw new IllegalArgumentException("entity id cannot be negative! (got: " + javaId + ")"); | ||
| } | ||
| return session.getEntityCache().getEntityByJavaId(javaId); | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable GeyserEntity byUuid(@NonNull UUID javaUuid) { | ||
| Objects.requireNonNull(javaUuid, "javaUuid"); | ||
| return session.getEntityCache().getEntityByUuid(javaUuid); | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable GeyserEntity byGeyserId(@NonNegative long geyserId) { | ||
| //noinspection ConstantValue | ||
| if (geyserId < 0) { | ||
| throw new IllegalArgumentException("geyser entity id cannot be negative! (got: " + geyserId + ")"); | ||
| } | ||
| return session.getEntityCache().getEntityByGeyserId(geyserId); | ||
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 130 out of 131 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (entity instanceof BoatEntity) { | ||
| // These bounding box values are based off 1.21.7 | ||
| entity.setBoundingBoxWidth(1.375F); | ||
| entity.setBoundingBoxHeight(0.5625F); | ||
| entity.updateBedrockMetadata(); | ||
| } else if (entity == null && this.vehicle instanceof BoatEntity) { | ||
| this.vehicle.setBoundingBoxWidth(this.vehicle.getDefinition().width()); | ||
| this.vehicle.setBoundingBoxHeight(this.vehicle.getDefinition().height()); | ||
| this.vehicle.setBoundingBoxWidth(this.vehicle.getBoundingBoxWidth()); | ||
| this.vehicle.setBoundingBoxHeight(this.vehicle.getBoundingBoxHeight()); | ||
| this.vehicle.updateBedrockMetadata(); | ||
| } |
There was a problem hiding this comment.
When dismounting a boat, this branch attempts to restore the boat bounding box back to its normal size, but it currently sets width/height to their existing values (no-op). As a result, the boat can remain with the enlarged bounding box after the player dismounts, contradicting the comment above and potentially affecting collision/movement. Restore the boat dimensions using the boat’s default size (e.g., from its type definition/base dimensions) instead of the current bounding box values.
…tities-api # Conflicts: # core/src/main/java/org/geysermc/geyser/entity/spawn/EntitySpawnContext.java # core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/Entity.java # core/src/main/java/org/geysermc/geyser/entity/type/EvokerFangsEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/LightningEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/MinecartEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/TextDisplayEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/animal/OcelotEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/merchant/VillagerEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/CreakingEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonPartEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EndermanEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/AvatarEntity.java # core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java # core/src/main/java/org/geysermc/geyser/entity/vehicle/BoatVehicleComponent.java # core/src/main/java/org/geysermc/geyser/entity/vehicle/VehicleComponent.java # core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java # core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java # core/src/main/java/org/geysermc/geyser/session/cache/BlockBreakHandler.java # core/src/main/java/org/geysermc/geyser/session/cache/BossBar.java # core/src/main/java/org/geysermc/geyser/session/cache/WorldBorder.java # core/src/main/java/org/geysermc/geyser/translator/inventory/chest/DoubleChestInventoryTranslator.java # core/src/main/java/org/geysermc/geyser/translator/level/event/PlaySoundEventTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java # core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerLookAtTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaGameEventTranslator.java # core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java # core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java
…nto feature/custom-entities-api
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 101 out of 102 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java:127
- ItemEntity's
offsetfield is updated inside thesetOffsetoverride, butmoveAbsoluteImmediateusesthis.offsetas the base offset and then callssetOffsetagain (including with a negated value when in water). This means that once the offset is negated, the next tick in water will negate it again, causing the entity to flip offset every tick. Keep an immutable/base offset (e.g., from the entity type/initial context) and only apply the temporary sign change to the Bedrock offset without overwriting the base.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public EntitySpawnContext(GeyserSession session, EntityTypeDefinition<?> definition, int javaId, UUID uuid, BedrockEntityDefinition bedrockEntityDefinition, Vector3f position, | ||
| Vector3f motion, float yaw, float pitch, float headYaw, float offset, @Nullable Long geyserId) { | ||
| this.session = session; | ||
| this.entityTypeDefinition = definition; | ||
| this.javaId = javaId; | ||
| this.uuid = uuid; | ||
| this.position = position; | ||
| this.offset = definition.offset(); | ||
| this.bedrockEntityDefinition = bedrockEntityDefinition; | ||
| this.motion = motion; |
There was a problem hiding this comment.
EntitySpawnContext's constructor accepts an offset parameter but ignores it and always assigns this.offset = definition.offset();. This makes it impossible to override the per-spawn offset (e.g., for event-driven/custom entity adjustments) and contradicts the call sites that pass an explicit offset value. Use the provided offset parameter when assigning this.offset (and ensure the other constructors pass the intended value).
Introducing: Custom Entities!
tl;dr: with this PR, you can summon custom bedrock entities that replace the vanilla mapping
Additions
CustomEntityDefinition/GeyserEntityDefinition: Representations of custom, and vanilla Bedrock entities. Unlike custom blocks/items, bedrock entities have fewer properties that are defined in advance; it's just the identifier, and the Bedrock Entity Properties. Setting entity properties was introduced in a previous PR; and works the same way.JavaEntityType: Represents a vanilla Java entity type, with the width / height / type identifier, as well as the default Bedrock entity associated with it. Similarly,CustomJavaEntityTyperepresents a non-vanilla Java entity - however, that part of the API still needs some more work and should be regarded as unstable.GeyserEntityDataType/GeyserEntityDataTypes: These are representations of various Bedrock entity metadata types, such as scale, width, height, variant, or color. Further,vertical_offsethas been added as a "custom" data type to allow setting a vertical entity offset.The
GeyserEntityclass has seen major additions! You can now query the entities' associated Bedrock entity definition, Java position, the Geyser id, UUID, or update / query the aforementioned data types.You can now look up
GeyserEntityinstances using the entity UUID or Geyser entity ID, additionally to the Java entity id.New Events
GeyserDefineEntitiesEvent: Allows registering custom Bedrock entities and querying existing entities.SessionSpawnEntityEvent: Base entity spawn event extended by the server events. With it, you can set a pre-spawn consumer, and switch the Bedrock entity definition, or cancel the entity spawn outright.ServerAttachParrotsEvent: Called every time a parrot is spawned on the player entityServerSpawnEntityEvent: Called for every non-player entity that is spawned by the Java server. Within this event, you can query the Java entity type, uuid, and entity id (and also have access to the methods provided by theSessionSpawnEntityEvent!Here's some example code of the API in action:
https://gist.github.com/onebeastchris/1521ab585669792a79a9558d9d069834
Internal changes:
EntitySpawnContextthat is passed in entity constructors - instead of many arguments. This should make it easier to add new entities, call events, or add more arguments in the futureEntityclass - no more hacks in TntEntity and the like!TO-DO's:
EXPERIMENTAL downloads: